package database

import (
	"context"
	"errors"
	"fmt"
	"log"
	"sync"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"go.mongodb.org/mongo-driver/mongo/readpref"
)

type TableInterface interface {
	GetCollecton() (*mongo.Collection, error)
	GetTableName() string
}

type Objs interface {
}

var (
	//DB_URI                   = "mongodb://root:123456@192.168.1.66:27017" // mongodb开启auth
	DB_URI  = "mongodb://192.168.1.66:27017" // mongodb关闭auth(安装mongodb后默认关闭鉴权)
	DB_NAME = "rabbit"                       // 数据库名

	TBLNAME_USER              = "user"              // 用户注册表
	TBLNAME_USER_TOKEN        = "token"             // 用户登录表
	TBLNAME_USER_RELATIONSHIP = "user_relationship" // 好友关系表
	TBLNAME_CHAT_MESSAGES     = "chat_messages"     // 聊天消息

	maxTime                  = time.Duration(5) // 链接超时时间
	DB_CLIENT_MAX_NUM uint64 = 100              // 链接数

	db_mutex       sync.Mutex
	toDB           *mongo.Database              // database 话柄
	collection_map map[string]*mongo.Collection // collection(相当于mysql中的数据表) 话柄
	db_ready       = false
)

// pool 连接池模式
func ConnectToDB(uri, name string, timeout time.Duration, num uint64) (*mongo.Database, error) {
	// 设置连接超时时间
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	// 通过传进来的uri连接相关的配置
	opts := options.Client().ApplyURI(uri)
	// 设置最大连接数 - 默认是100 ，不设置就是最大 max 64
	opts.SetMaxPoolSize(num)
	// 发起链接
	client, err := mongo.Connect(ctx, opts)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	// 判断服务是不是可用
	if err = client.Ping(context.Background(), readpref.Primary()); err != nil {
		fmt.Println(err)
		return nil, err
	}
	// 返回 client
	return client.Database(name), nil
}

func init() {
	var err error
	collection_map = make(map[string]*mongo.Collection)

	condb := func() error {
		db_mutex.Lock()
		defer db_mutex.Unlock()
		fmt.Println("----------------------ready connect to mongodb!")
		toDB, err = ConnectToDB(DB_URI, DB_NAME, maxTime, DB_CLIENT_MAX_NUM)
		if err != nil {
			db_ready = false
			fmt.Println("----------------------failed connectto mongodb!")
			return err
		}
		fmt.Println("----------------------connectted to mongodb!")

		db_ready = true
		collection_map[TBLNAME_USER] = toDB.Collection(TBLNAME_USER)
		collection_map[TBLNAME_USER_TOKEN] = toDB.Collection(TBLNAME_USER_TOKEN)
		collection_map[TBLNAME_USER_RELATIONSHIP] = toDB.Collection(TBLNAME_USER_RELATIONSHIP)
		collection_map[TBLNAME_CHAT_MESSAGES] = toDB.Collection(TBLNAME_CHAT_MESSAGES)
		return nil
	}
	condb()

	go func() {
		for {
			timeout := time.After(time.Second * 4)
			<-timeout
			if toDB != nil {
				if err := toDB.Client().Ping(context.Background(), readpref.Primary()); err != nil {
					fmt.Println(err)

					fmt.Println(" retry connect db")
					condb()
				}
			} else {
				condb()
			}
		}
	}()
}

func ExitDb() {
	if toDB != nil {
		toDB.Client().Disconnect(context.Background())
	}
}

// 获取数据表手柄
func GetCollecton(TableName string) (*mongo.Collection, error) {
	db_mutex.Lock()
	defer db_mutex.Unlock()

	if !db_ready {
		return nil, errors.New("db disconnected, waitting for retry connect")
	}

	collection, ok := collection_map[TableName]
	if !ok {
		fmt.Println("can't find collection")
		return nil, errors.New("can't find collection")
	}

	return collection, nil
}

// GetList 获取过滤器指定的数据，写入objs对象中
func GetList(c, filter, objs interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return err
	}

	cur, err := collection.Find(context.Background(), filter)
	if err != nil {
		fmt.Println(err)
		return err
	}
	if err := cur.Err(); err != nil {
		fmt.Println(err)
		return err
	}
	err = cur.All(context.Background(), &objs)
	if err != nil {
		fmt.Println(err)
		return err
	}
	cur.Close(context.Background())

	log.Println("collection.Find curl.All: ", objs)

	return nil
}

// AddOne 新增一条数据
func AddOne(c interface{}) (*mongo.InsertOneResult, error) {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return nil, err
	}

	objId, err := collection.InsertOne(context.TODO(), c)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	log.Println("录入数据成功，objId:", objId)

	return objId, nil
}

// EditOne 编辑一条数据
func EditOne(c, filter interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return err
	}

	update := bson.M{"$set": c}
	updateResult, err := collection.UpdateOne(context.Background(), filter, update)
	if err != nil {
		fmt.Println(err)
		return err
	}
	log.Println("collection.UpdateOne:", updateResult)

	return nil
}

// 更新数据 - 存在更新，不存在就新增
func Update(c, filter interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return err
	}

	update := bson.M{"$set": c}
	updateOpts := options.Update().SetUpsert(true)
	updateResult, err := collection.UpdateOne(context.Background(), filter, update, updateOpts)
	if err != nil {
		fmt.Println(err)
	}
	log.Println("collection.UpdateOne:", updateResult)

	return nil
}

// 删除一条匹配的数据
func Del(c, filter interface{}) (*mongo.DeleteResult, error) {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return nil, err
	}

	deleteResult, err := collection.DeleteOne(context.Background(), filter)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	fmt.Println("collection.DeleteOne:", deleteResult)

	return deleteResult, nil
}

// 删除全部匹配的数据
func DelAll(c, filter interface{}) (*mongo.DeleteResult, error) {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return nil, err
	}

	deleteResult, err := collection.DeleteMany(context.Background(), filter)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	log.Println("collection.DeleteOne:", deleteResult)

	return deleteResult, nil
}

// 删除整个文档
func DelTable(c interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return nil
	}

	err = collection.Drop(context.Background())
	if err != nil {
		fmt.Println(err)
	}

	return nil
}

// Sectle 模糊查询
// bson.M{"name": primitive.Regex{Pattern: "深入"}}
func Sectle(c, filter interface{}, objs interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return err
	}

	cur, err := collection.Find(context.Background(), filter)
	if err != nil {
		fmt.Println(err)
		return err
	}
	if err := cur.Err(); err != nil {
		fmt.Println(err)
	}

	switch c.(TableInterface).GetTableName() {
	case TBLNAME_USER:
		ss := []UserInfo{}
		for cur.Next(context.Background()) {
			var s UserInfo
			if err = cur.Decode(s); err != nil {
				fmt.Println(err)
			}
			//log.Println("collection.Find name=primitive.Regex{xx}: ", s)
			ss = append(ss, s)
		}
		cur.Close(context.Background())
		objs = ss
	case TBLNAME_USER_RELATIONSHIP:
		ss := []UserRelationship{}
		for cur.Next(context.Background()) {
			var s UserRelationship
			if err = cur.Decode(s); err != nil {
				fmt.Println(err)
			}
			//log.Println("collection.Find name=primitive.Regex{xx}: ", s)
			ss = append(ss, s)
		}
		cur.Close(context.Background())
		objs = ss
	case TBLNAME_CHAT_MESSAGES:
		ss := []ChatMessages{}
		for cur.Next(context.Background()) {
			var s ChatMessages
			if err = cur.Decode(s); err != nil {
				fmt.Println(err)
			}
			//log.Println("collection.Find name=primitive.Regex{xx}: ", s)
			ss = append(ss, s)
		}
		cur.Close(context.Background())
		objs = ss

	}

	return nil
}

// 统计collection的数据总数
func Count(c, filter interface{}) (int, error) {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return 0, err
	}

	count, err := collection.CountDocuments(context.Background(), filter)
	if err != nil {
		fmt.Println(count)
	}
	log.Println("collection.CountDocuments:", count)

	return int(count), nil
}

// 准确搜索一条数据
func GetOne(c, filter interface{}) error {
	collection, err := GetCollecton(c.(TableInterface).GetTableName())
	if err != nil {
		return err
	}

	err = collection.FindOne(context.Background(), filter).Decode(c)
	if err != nil {
		fmt.Println(err)
		return err
	}
	log.Println("collection.FindOne: ", c)
	return nil
}
